package com.hww.slow_sql;

import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Properties;

/**
 * 慢SQL拦截
 *
 * @author xiaojie
 * @date 2021/1/9 7:26 下午
 */
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
})
@Slf4j
public class SlowSQLInterceptor implements Interceptor {

    /**
     * 配置文件
     */
    private final SlowSQLConfig slowSQLConfig;

    /**
     * 慢SQL 信息处理器
     */
    private final SlowSQLProcessor slowSQLProcessor;

    public SlowSQLInterceptor(SlowSQLConfig slowSQLConfig, SlowSQLProcessor slowSQLProcessor) {
        this.slowSQLConfig = slowSQLConfig;
        this.slowSQLProcessor = slowSQLProcessor;
    }


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        Object result = null;
        if (target instanceof Executor) {
            long start = System.currentTimeMillis();
            long end = -1L;
            Throwable curException = null;
            try {
                result = invocation.proceed();
                end = System.currentTimeMillis();
            } catch (Throwable e) {
                log.error(e.getMessage(), e);
                curException = e;
                end = System.currentTimeMillis();
            }
            // 校验是否是慢SQL或者出现异常了
            if (hasErrorOrSlowSQL(start, end, curException)) {
                // 异常信息报告
                reportErrorOrSlowSQL(start, end, invocation, curException);
            }
            // 异常直接抛出
            if (curException != null) {
                throw curException;
            }
            // 结果集数量判断
            int queryRowCount = -1;
            if ((queryRowCount = checkBigDataQuery(result)) > 0) {
                // 推送消息
                reportErrorOrSlowSQL(start, end, invocation, curException, queryRowCount);
            }
        }
        return result;
    }

    /**
     * 校验大数据量的查询
     *
     * @param result
     * @return
     */
    protected int checkBigDataQuery(Object result) {
        if (ObjectUtil.isEmpty(result)) {
            return SlowSQLConfig.DEFAULT_SLOWSQL_ROWCOUNT;
        }
        // 判断是否是集合
        if (result instanceof Collection) {
            Collection<?> coll = (Collection<?>) result;
            return coll.size() >= this.slowSQLConfig.getMaxRowsCount() ? coll.size() : SlowSQLConfig.DEFAULT_SLOWSQL_ROWCOUNT;
        }
        // 判断数组
        if (result.getClass().isArray()) {
            return Array.getLength(result) >= this.slowSQLConfig.getMaxRowsCount() ? Array.getLength(result) : SlowSQLConfig.DEFAULT_SLOWSQL_ROWCOUNT;
        }
        return SlowSQLConfig.DEFAULT_SLOWSQL_ROWCOUNT;
    }

    /**
     * 处理异常SQL信息
     *
     * @param start
     * @param end
     * @param invocation
     * @param curException
     */
    protected void reportErrorOrSlowSQL(long start, long end, Invocation invocation, Throwable curException) {
        // 普通的慢SQL或者异常SQL
        reportErrorOrSlowSQL(start, end, invocation, curException, SlowSQLConfig.DEFAULT_SLOWSQL_ROWCOUNT);
    }

    /**
     * 大数据量查询
     *
     * @param start
     * @param end
     * @param invocation
     * @param curException
     * @param rowCount
     */
    protected void reportErrorOrSlowSQL(long start, long end, Invocation invocation, Throwable curException, int rowCount) {
        try {

            this.slowSQLProcessor.addSlowSQLInfo(new SlowSQLDTO(start, end, invocation, curException, rowCount, ""));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 校验是否存在错误或者慢SQL
     *
     * @param start
     * @param end
     * @param curException
     * @return
     */
    protected boolean hasErrorOrSlowSQL(long start, long end, Throwable curException) {
        if (curException != null) {
            return true;
        }
        if ((end - start) >=
                ((this.slowSQLConfig.getSlowSqlTime() <= 0 ? SlowSQLConfig.DEFAULT_SLOWSQL_TIME : this.slowSQLConfig.getSlowSqlTime()) * 1000)) {
            return true;
        }
        return false;
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }


    @Override
    public void setProperties(Properties properties) {

    }

}
